
系列:30 天打造企業級 PLM|面向:後端|素材:安全稽核記錄、實際修補歷程
功能都做完了,換個角度問:如果我是攻擊者,這套系統哪裡最好下手?這是上線前的自我滲透盤點。
內網系統的資安常被「反正在內網」四個字打發。但把威脅模型攤開來看,內網從來不是一道牆,而是一個房間,房間裡站著這些人:離職前把整個 BOM 下載一份的工程師、中毒後被當跳板的產線電腦、被釣魚拿到工號密碼的採購、還有拿到測試帳號卻能看到全公司料件的外包廠商。這四種角色沒有一個需要突破防火牆,他們本來就在裡面。
今天講 Mini-PLM 的資安強化實錄:一次盤點、一串分級修補,以及兩個資安與需求的拉鋸。
以前在維運 Oracle Agile PLM 時,資安團隊最大的夢魘往往來自底層的 WebLogic 容器:
Mini-PLM 採用現代 Spring Security 應用層深度防禦體系,將所有安全規則、帳號防爆破計數、JWT 撤銷清單完全納入 Git 版控與自動化測試管線中。這個轉換最實際的紅利不是「更安全」,而是安全規則從此可以被 code review、被測試、被 diff——上一次誰放寬了哪條規則,git blame 說了算。
盤點不從功能清單開始,功能清單會漏掉所有你忘記自己做過的端點。盤點從 WebSecurityConfig(Day 6)開始:把每一條 permitAll 與 authenticated 規則列出來,逐條問三個問題——這端點匿名者拿得到什麼?任意登入者拿得到什麼?它該被誰拿到?

這張圖的重點在「方向」:多數人做資安盤點是從功能往下找端點,結果永遠漏掉那些沒人記得的舊路由;從 Security 設定往上反推則不會漏,因為任何進得來的請求都必須先經過這張表。這是 Day 6 把授權規則簡化成三級的資安紅利:攻擊面的地圖只有一頁,一個下午看得完。
盤出來的發現按「嚴重度 × 修補成本」分級。嚴重度不是憑感覺,問的是「這筆資料外流,最壞會發生什麼」——未發布的新品料號流到競爭對手,跟少一筆稽核軌跡,不是同一個量級。
檔案下載授權的三階段(Day 25 詳述)是這個方法的樣板:先擋匿名、再收權限、最後補稽核。每一步影響面可解釋、可回退。
為什麼不一次到位?因為授權收緊的影響面永遠比想像中大:你不知道有多少報表、爬蟲、Excel 巨集、甚至隔壁部門自己寫的小工具,正靠著那個沒人管的匿名端點在跑。一次到位的大收緊,最常見的下場是上線當天業務癱瘓、全部 rollback、資安專案無限期擱置——回退掉的不只是那次改動,是整個團隊對資安改善的耐心。分階段的價值在於每一階段都有機會發現「原來還有人這樣用」,而不是全部一起爆。
機制本身是教科書:登入失敗計數、達門檻自動鎖(限時)、管理員可手動鎖與解鎖、成功登入歸零計數。值得講的是它曾經整組失效的原因。

斷鏈點在圖上只有一個箭頭的距離:鎖定判斷讀的是 principal 上的三個欄位,而組 principal 的那段 JPQL projection(Day 7 的坑)沒把它們選進來,於是每次登入拿到的都是「沒鎖、沒到期、失敗零次」。所有分支都照著這組空值走,判斷邏輯本身完全正確,正確地得出錯誤結論。功能的每一環都對,資料流斷一節就全盤失效。
資安功能失效尤其危險,因為沒有任何症狀。業務功能壞了會有人抱怨,鎖定機制壞了只會安靜地放行。沒人被鎖不等於沒人在爆破,很可能剛好相反。所以這類機制要有刻意的失效測試當健康檢查:故意打錯密碼 N 次、驗證真的鎖了、等到期、驗證真的解了。這個案例之後,這串驗證被寫成固定的測試而不是口頭 SOP——防線要有人定期去撞它,才知道它還在。
配套一併盤點:手動鎖定、強制登出(revoke tokens,JWT stateless 的補償機制:token 撤銷清單)。這批管理端點在 Security 規則裡明確排在 authenticated 之前限定 ADMIN,Day 6 那條「順序敏感」的註解就是為它們寫的——規則寫對了但擺錯位置,等於沒寫。
滲透視角的發現:登入 API 對「帳號不存在」回 ACCOUNT_NOT_FOUND,對「密碼錯誤」回另一種回應。攻擊者可以批量探測哪些帳號存在(枚舉),再對存在的帳號集中爆破。資安的標準答案很乾脆:兩種情況回一模一樣的錯誤。
但產品需求明著要「讓使用者知道帳號打錯了」。廠區使用者常打錯工號,模糊訊息會塞爆支援電話——這不是想像中的成本,是前一套系統實際發生過的事。
這是真衝突,沒有雙贏解。處理方式是把衝突寫成紀錄:
資安不是永遠贏,但每次讓步都要有字據。沒有字據的讓步,半年後會變成「當初為什麼這樣做沒人記得」,然後在稽核時被當成疏漏而不是決策。
檔案下載的授權洞修補時,同型端點(各種 by-id、by-uuid、預覽、縮圖)一起掃。補一個漏十個是授權洞的常態,因為漏洞的根源通常不是某行程式碼寫錯,而是某個當年的設計假設——「檔案 UUID 猜不到所以不用擋」——而那個假設會原封不動地複製到每一個相似端點。
修一個端點只是修掉假設的一個實例。所以修補 PR 的 checklist 固定包含:列出所有同 pattern 端點、逐一標記已修/不適用/待修,這份清單進 PR 描述。Reviewer 審的不是那幾行 diff,是這份清單完不完整。
'Y' = 'Y ' 的詭異比對行為在兩庫間不一致,Day 27 的親戚。旗標欄位常被用在「是否鎖定」這種安全判斷上,比對失準就是靜默放行從 Security 設定反推攻擊面,分階段收斂,用失效測試驗證防線真的在防,衝突明著取捨留字據,修洞掃同型端點。
這五件事的共同點是:把資安從「感覺安全」變成可以被檢查的東西——一張可以逐條走的地圖、一份有 owner 的清單、一組會失敗的測試、一份寫下來的取捨紀錄。進階主題週到此完結,明日 Day 29:部署上線,最後一哩路。